#ifndef TYPE_LIST_H
#define TYPE_LIST_H

#include <tuple>
#include <type_traits>

//! a tag for null type
struct null_type;

/**
 *  @brief  type_list
 */
template <typename... Args> struct type_list;	//!< prototype never used

template <>
struct type_list<null_type>
{
	typedef null_type head_type;
	typedef null_type next;
	typedef std::tuple<null_type> tuple_type;	
	static constexpr int num_types = 0;

	template <typename T2>
	struct append_type
	{ typedef type_list<T2> result_type; };
};

template <typename T>
struct type_list<T>
{
	typedef T head_type;
	typedef type_list<null_type> next;
	typedef std::tuple<T> tuple_type;	
	static constexpr int num_types = 1;

	template <typename T2>
	struct append_type
	{ typedef type_list<T2, T> result_type; };
};

template <typename T, typename ... Args>
struct type_list<T, Args...>
{
	typedef T head_type;
	typedef type_list<Args...> next;
	typedef std::tuple<T, Args...> tuple_type;
	static constexpr int num_types = 1 + sizeof...(Args);

	template <typename T2>
	struct append_type
	{ typedef type_list<T2, T, Args...> result_type; };
};

/**
 *  @brief  check_type_inside. Check if type T in type list t_list
 */
template <typename T, class t_list>
struct check_type_inside 
{
	static constexpr bool value = 
		std::is_same<T, typename t_list::head_type>::value || 
		check_type_inside<T, typename t_list::next>::value;
};

template <typename T>
struct check_type_inside <T, type_list<null_type>>
{  static constexpr bool value = std::is_same<T, type_list<null_type>>::value; };

/**
 *  @brief  retrieve_type. Retrieve the id-th type in type list t_list
 */
template <int id, class t_list>
struct retrieve_type 
{
	typedef typename std::conditional<
		(t_list::num_types - 1 == id), 
		typename t_list::head_type, 
		typename retrieve_type<id, typename t_list::next>::result_type 
	>::type result_type;
};

template <int id>
struct retrieve_type<id, null_type>
{  
	typedef null_type result_type;  
};

/**
 *  @brief  index_of. Look up the first occurence of type T in type list t_list
 */
template <typename T, class t_list>
struct index_of
{
	static constexpr int value = 
		std::is_same<T, typename t_list::head_type>::value ? 
		t_list::num_types - 1
		: index_of<T, typename t_list::next>::value;
};

template <typename T>
struct index_of<T, type_list<null_type>>
{
	static constexpr int value = -1;
};

/**
 *  @brief  all are same as type T. Check if all types in t_list are same as type T
 */
template <typename T, class t_list, bool derived_included = false>
struct all_same_as_type
{
	static constexpr bool same_type = derived_included ?
		std::is_base_of<T, typename t_list::head_type>::value :
		std::is_same<T, typename t_list::head_type>::value;
		
	static constexpr bool value = true && same_type
		&& all_same_as_type<T, typename t_list::next>::value;
};

template <typename T, bool derived_included>
struct all_same_as_type<T, type_list<null_type>, derived_included>
{
	static constexpr int value = true;
};

/**
 *  @brief  number of type T conatined. How many type of T caontained in type list t_list?
 */
template <typename T, class t_list, bool derived_included = false>
struct num_of_type_inside
{
	static constexpr unsigned int count_base = std::is_same<T, typename t_list::head_type>::value ? 1 : 0;
	static constexpr unsigned int count_derived = std::is_base_of<T, typename t_list::head_type>::value ? 1 : 0;
	static constexpr unsigned int count_total = derived_included ? count_derived : count_base;
	static constexpr unsigned int value = count_total +
		num_of_type_inside<T, typename t_list::next, derived_included>::value;
};

template <typename T, bool derived_included>
struct num_of_type_inside <T, type_list<null_type>, derived_included>
{  static constexpr unsigned int value = 0; };

/**
 *  @brief  repeat_N_types_as_type_list. Repeat type T in N times, so result_type is type_list<T, T, T,...>
 */

template <typename T, int N>
struct repeat_N_types_as_type_list
{
	typedef typename repeat_N_types_as_type_list<T, N-1>::result_type::template append_type<T>::result_type result_type;
};

template <typename T>
struct repeat_N_types_as_type_list<T, 1>
{
	typedef type_list<T> result_type;
};

#endif //!< TYPE_LIST_H